package cc.rengu.igas.route.core.realize.impl;

import cc.rengu.igas.route.common.constant.SmartRouteParamConstant;
import cc.rengu.igas.route.common.entity.MultiDyncDataStatic;
import cc.rengu.igas.route.common.entity.RuleCfg;
import cc.rengu.igas.route.common.enums.RouteTxnNumEnum;
import cc.rengu.igas.route.common.enums.SmartRouteRspEnums;
import cc.rengu.igas.route.core.helper.RouterHelper;
import cc.rengu.igas.route.core.model.*;
import cc.rengu.igas.route.core.realize.RouterChoice;
import cc.rengu.oltp.service.common.constant.AppParamConstant;
import cc.rengu.oltp.service.common.dao.DstChannelInfoMapper;
import cc.rengu.oltp.service.common.dao.DstTxnAuthCfgMapper;
import cc.rengu.oltp.service.common.dao.ChannelCallCfgMapper;
import cc.rengu.oltp.service.common.dao.impl.DstChannelInfoMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.DstTxnAuthCfgMapperImpl;
import cc.rengu.oltp.service.common.dao.impl.ChannelCallCfgMapperImpl;
import cc.rengu.oltp.service.common.entity.DstChannelInfo;
import cc.rengu.oltp.service.common.entity.DstTxnAuthCfg;
import cc.rengu.oltp.service.common.entity.SysParam;
import cc.rengu.oltp.service.common.entity.ChannelCallCfg;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.realize.SysParamService;
import cc.rengu.oltp.service.realize.impl.SysParamServiceImpl;
import cc.rengu.oltp.utility.util.RedisUtil;
import cc.rengu.utility.base.StringUtil;
import cc.rengu.utility.cache.UnifiedCache;
import cc.rengu.utility.log.RgLog;
import cc.rengu.utility.log.RgLogger;
import com.alibaba.fastjson.JSON;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 多路比例路由实现类
 *
 * @author xionglz
 * @version 1.0
 * @date 2020-03-23
 */
public class MultiRatioRouterChoiceImpl implements RouterChoice {
    private RgLogger rglog = RgLog.getLogger(this.getClass().getName());

    @Override
    public List<ProdInfoBean> routerChoice(RuleCfg ruleCfg, RouterReq routerReq) throws Exception {
        RouteTxnNumEnum txnNumEnum = RouteTxnNumEnum.getInstanceFromTxnGroup(ruleCfg.getTxnGroup());
        Optional.ofNullable(txnNumEnum).orElseThrow(() -> {
            rglog.warn("<{}>不支持的业务类型", ruleCfg.getTxnGroup());
            return new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        });
        //选择通道
        DstChannelInfoBean dstChannelInfo = choice(ruleCfg, routerReq);
        if (null == dstChannelInfo) {
            rglog.error("通道选择失败,无可用通道");
            throw new BizException(SmartRouteRspEnums.NO_AVAILABLE_DST_CHANNEL_ERROR.getRespCode(), SmartRouteRspEnums.NO_AVAILABLE_DST_CHANNEL_ERROR.getRespDesc());
        }
        //查询redis，不存在或者失败再降级查询数据库
        ChannelCallCfgMapper channelCallCfgMapper = new ChannelCallCfgMapperImpl();
        List<ChannelCallCfg> ChannelCallCfgs = channelCallCfgMapper.selectChannelCallCfgByTxnNum(ruleCfg.getInstId(), SmartRouteParamConstant.IGRT,
                SmartRouteParamConstant.ALL, txnNumEnum.getTxnNum(), SmartRouteParamConstant.STRING_TWO_ZERO);
        Optional.ofNullable(ChannelCallCfgs).orElseThrow(() -> {
            rglog.error("获取交易码映射表失败");
            return new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        });
        String dstChannelId = dstChannelInfo.getDstChannelInfo().getDstChannelId();
        if (null == dstChannelId) {
            rglog.info("获取支付通道失败");
            throw new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        }
        Optional<ChannelCallCfg> channelCallCfgOpt = ChannelCallCfgs.stream().filter(item -> dstChannelId.equals(item.getCallChannelId())).findFirst();
        if (!channelCallCfgOpt.isPresent()) {
            rglog.info("获取通道服务调用配置失败");
            throw new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        }
        ChannelCallCfg channelCallCfg = channelCallCfgOpt.get();
        //获取通道交易权限信息
        DstTxnAuthCfg dstTxnAuthCfg = null;
        String dstTxnAuthKey = SmartRouteParamConstant.ROUTE_CONF_DST_TXN_AUTH + ":" + channelCallCfg.getInstId() + ":" + channelCallCfg.getCallChannelId() + ":" + channelCallCfg.getCallChannelTxn();
        try {
            dstTxnAuthCfg = JSON.parseObject(RedisUtil.onceGet(dstTxnAuthKey), DstTxnAuthCfg.class);
        } catch (Exception e) {
            rglog.warn("redis中未查询到key<{}>通道交易权限信息", dstTxnAuthKey);
        }
        if (null == dstTxnAuthCfg) {
            //查询数据库
            DstTxnAuthCfgMapper dstTxnAuthCfgMapper = new DstTxnAuthCfgMapperImpl();
            dstTxnAuthCfg = dstTxnAuthCfgMapper.selectDstTxnAuthCfgByPrimaryKey(channelCallCfg.getInstId(), channelCallCfg.getCallChannelId(), channelCallCfg.getCallChannelTxn());
        }
        if (null == dstTxnAuthCfg) {
            rglog.warn("该通道<{}：{}：{}>没有配置支持智能路由的通道交易", channelCallCfg.getInstId(), channelCallCfg.getCallChannelId(), channelCallCfg.getCallChannelTxn());
            throw new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        }
        //返回通道
        List<ProdInfoBean> listProdInfo = new ArrayList<>();
        ProdInfoBean prodInfo = new ProdInfoBean();
        prodInfo.setDstChannelId(dstChannelInfo.getDstChannelInfo().getDstChannelId());
        prodInfo.setProdDesc(dstChannelInfo.getDstChannelInfo().getDstChannelName());
        prodInfo.setProdSts(dstChannelInfo.getDstChannelInfo().getDstChannelStatus());
        prodInfo.setPriority(SmartRouteParamConstant.STRING_ONE_HUNDRED);
        prodInfo.setDstChannelTxnImplClassName(dstChannelInfo.getDstChannelTxnImplClassName());
        prodInfo.setDstChannelTxnNum(dstTxnAuthCfg.getChannelTxn());
        listProdInfo.add(prodInfo);
        return listProdInfo;

    }

    /**
     * 计算比例路由最优通道，
     *
     * @param ruleCfg   规则配置
     * @param routerReq 查询请求
     * @return DstChannelInfo 成功返回通道信息
     * @throws BizException 失败抛出业务异常
     */
    private DstChannelInfoBean choice(RuleCfg ruleCfg, RouterReq routerReq) throws Exception {
        //从缓存中获取多路比例路由动态统计数据
        String keyCache = ruleCfg.getInstId() + ":" + ruleCfg.getRuleId();
        MultiDyncDataRuleGroup dyncDataRuleGroup = (MultiDyncDataRuleGroup) UnifiedCache.get(SmartRouteParamConstant.ROUTE_DATA_MULTI, keyCache);
        if (null == dyncDataRuleGroup) {
            //存在修改静态路由为多路比例和智能路由的情况，不能报错，需要新建多路比例路由的统计数据
            //初始化涉及到并发问题，因此需要全局锁
            RouterDataInitMultiDyncData routerDataInitMultiDyncData = new RouterDataInitMultiDyncData();
            if (!RouterDataInitMultiDyncData.isUpdate()) {
                routerDataInitMultiDyncData.loadRouterData();
            }
            //若正在更新,则同步等待更新完成，最大等待时间3秒，3秒后若无数据则失败
            else {
                routerDataInitMultiDyncData.waitload();
            }
            rglog.warn("多路比例路由动态统计参数获取失败，keyCache<{}>", keyCache);
            dyncDataRuleGroup = (MultiDyncDataRuleGroup) UnifiedCache.get(SmartRouteParamConstant.ROUTE_DATA_MULTI, keyCache);
            if (null == dyncDataRuleGroup) {
                rglog.error("再次初始化后出错，多路比例路由动态统计参数获取失败，keyCache<{}>", keyCache);
                return null;
            }
        }
        if (null == dyncDataRuleGroup.getMultiDyncDataRuleArrayList() || dyncDataRuleGroup.getMultiDyncDataRuleArrayList().isEmpty()) {
            rglog.error("多路比例路由动态统计参数为空，keyCache<{}>", keyCache);
            return null;
        }

        //根据机构id,源系统标识，交易码，业务分类查询通道服务调用配置表
        RouteTxnNumEnum txnNumEnum = RouteTxnNumEnum.getInstanceFromTxnGroup(ruleCfg.getTxnGroup());
        Optional.ofNullable(txnNumEnum).orElseThrow(() -> {
            rglog.warn("<{}>不支持的业务类型", ruleCfg.getTxnGroup());
            return new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        });
        //查询redis，不存在或者失败再降级查询数据库
        ChannelCallCfgMapper channelCallCfgMapper = new ChannelCallCfgMapperImpl();
        List<ChannelCallCfg> ChannelCallCfgs = channelCallCfgMapper.selectChannelCallCfgByTxnNum(ruleCfg.getInstId(), SmartRouteParamConstant.IGRT,
                SmartRouteParamConstant.ALL, txnNumEnum.getTxnNum(), SmartRouteParamConstant.STRING_TWO_ZERO);
        if (null == ChannelCallCfgs || ChannelCallCfgs.isEmpty()) {
            rglog.error("获取交易码映射表失败");
            throw new BizException(SmartRouteRspEnums.CONFIG_ERROR.getRespCode(), SmartRouteRspEnums.CONFIG_ERROR.getRespDesc());
        }

        DstChannelInfoBean dstChannelInfoBean = new DstChannelInfoBean();
        //路由的通道若不可用则其使用量+1之后继续下一个通道，其中若存在一个可用通道则直接返回，否则直至所有通道都尝试一遍之后返回失败
        int maxLoop = dyncDataRuleGroup.getMultiDyncDataRuleArrayList().size();
        for (int i = 0; i < maxLoop; i++) {
            //获取当前的通道
            MultiDyncDataStatic multiDyncDataStatic = dyncDataRuleGroup.getNowDstChannelDyncData();
            DstChannelInfo dstChannelInfo = null;
            //查询redis获取通道信息
            String dstChannelKey = ruleCfg.getInstId().trim() + multiDyncDataStatic.getChannelId().trim();
            try {
                dstChannelInfo = JSON.parseObject(RedisUtil.hashGet(AppParamConstant.CHANNEL_ISS_INFO_CACHE, dstChannelKey), DstChannelInfo.class);
            } catch (Exception e) {
                //抓取redis的RuntimeException类型异常
                rglog.warn("读取redis异常{}", StringUtil.ExceptionToString(e));
            }
            if (null == dstChannelInfo) {
                DstChannelInfoMapper dstChannelInfoMapper = new DstChannelInfoMapperImpl();
                dstChannelInfo = dstChannelInfoMapper.selectDstChannelInfoByPrimaryKey(ruleCfg.getInstId().trim(), multiDyncDataStatic.getChannelId());
            }
            if (null == dstChannelInfo) {
                rglog.error("支付通道<{}-{}>未配置或已停用！", multiDyncDataStatic.getChannelId());
                //当前通道为最后一个通道则返回失败
                if (i == maxLoop - 1) {
                    rglog.warn("所有通道都不可用");
                    return null;
                }
                continue;
            }
            //排除本行通道
            if (SmartRouteParamConstant.ESB.equalsIgnoreCase(dstChannelInfo.getDstChannelId()) ||
                    SmartRouteParamConstant.ESBMS.equalsIgnoreCase(dstChannelInfo.getDstChannelId())) {
                continue;
            }
            //获取到通道对应的ChannelCallCfg
            ChannelCallCfg channelCallCfg = null;
            for (ChannelCallCfg channelCallCfgTmp : ChannelCallCfgs) {
                if (channelCallCfgTmp.getInstId().equals(dstChannelInfo.getInstId()) && channelCallCfgTmp.getCallChannelId().equals(dstChannelInfo.getDstChannelId())) {
                    channelCallCfg = channelCallCfgTmp;
                    break;
                }
            }
            if (null == channelCallCfg) {
                rglog.error("通道<{}>配置错误未获取到交易码映射信息！", dstChannelInfo.getDstChannelId());
                if (i == maxLoop - 1) {
                    rglog.warn("所有通道都不可用");
                    return null;
                }
                continue;
            }
            //获取通道交易权限信息
            DstTxnAuthCfg dstTxnAuthCfg = null;
            String dstTxnAuthKey = SmartRouteParamConstant.ROUTE_CONF_DST_TXN_AUTH + ":" + channelCallCfg.getInstId() + ":" + channelCallCfg.getCallChannelId() + ":" + channelCallCfg.getCallChannelTxn();
            try {
                dstTxnAuthCfg = JSON.parseObject(RedisUtil.onceGet(dstTxnAuthKey), DstTxnAuthCfg.class);
            } catch (Exception e) {
                rglog.warn("redis中未查询到key<{}>通道交易权限信息", dstTxnAuthKey);
            }
            if (null == dstTxnAuthCfg) {
                //查询数据库
                DstTxnAuthCfgMapper dstTxnAuthCfgMapper = new DstTxnAuthCfgMapperImpl();
                dstTxnAuthCfg = dstTxnAuthCfgMapper.selectDstTxnAuthCfgByPrimaryKey(channelCallCfg.getInstId(), channelCallCfg.getCallChannelId(), channelCallCfg.getCallChannelTxn());
            }
            if (null == dstTxnAuthCfg) {
                rglog.warn("该通道<{}：{}：{}>没有配置支持智能路由的通道交易", channelCallCfg.getInstId(), channelCallCfg.getCallChannelId(), channelCallCfg.getCallChannelTxn());
                if (i == maxLoop - 1) {
                    rglog.warn("所有通道都不可用");
                    return null;
                }
                continue;
            }
            //根据通道标识，通道交易码查询系统参数表获取对应实现类
            String type = SmartRouteParamConstant.PARAM_TYPE_PREFIX_CHANNEL_INSTANCE + dstChannelInfo.getDstChannelId();
            SysParamService sysParamService = new SysParamServiceImpl();
            String txnNum;
            //由于签约使用的是代收的路由方式，因此签约需要特殊处理
            if (RouteTxnNumEnum.PAYMENT_SIGN.getTxnNum().equals(routerReq.getTxnNum()) ||
                    RouteTxnNumEnum.PAYMENT_SIGN_VERIFY.getTxnNum().equals(routerReq.getTxnNum())) {
                txnNum = routerReq.getTxnNum();
            } else {
                txnNum = channelCallCfg.getTxnNum();
            }
            SysParam sysParam = sysParamService.getSysParamInfo(dstChannelInfo.getInstId(), type, txnNum);
            if (sysParam == null || cc.rengu.oltp.utility.util.StringUtil.isEmptyOrNull(sysParam.getParamValue()) || !SmartRouteParamConstant.YES.equalsIgnoreCase(sysParam.getParamStatus())) {
                rglog.warn("根据通道参数类型<:{}>，交易码<txnNum:{}>未找到机构<{}>客户化实现类或者通用实现类", type, txnNum, dstChannelInfo.getInstId());
                continue;
            }
            CompleteRouteInfoBean completeRouteInfoBean = new CompleteRouteInfoBean(dstChannelInfo, channelCallCfg, dstTxnAuthCfg);
            completeRouteInfoBean.setDstChannelTxnImplClassName(sysParam.getParamValue());
            if (!dstChannelCheck(routerReq, completeRouteInfoBean)) {
                rglog.warn("当前通道<{}:{}>不可用，直接尝试下一个通道，并且当前通道的使用率也同步增加", dstChannelInfo.getDstChannelId(), dstChannelInfo.getDstChannelName());
                //当前通道为最后一个通道则返回失败
                if (i == maxLoop - 1) {
                    rglog.warn("所有通道都不可用");
                    return null;
                }
            } else {
                dstChannelInfoBean.setDstChannelTxnImplClassName(sysParam.getParamValue());
                dstChannelInfoBean.setDstChannelInfo(dstChannelInfo);
                break;
            }
        }
        return dstChannelInfoBean;
    }

    private boolean dstChannelCheck(RouterReq routerReq, CompleteRouteInfoBean completeRouteInfoBean) throws Exception {
        if (null == routerReq || null == completeRouteInfoBean) {
            return false;
        }
        if (null == completeRouteInfoBean.getDstChannelInfo()) {
            return false;
        }
        //判断通道通用性阻断条件
        DstChannelFilterModel dstChannelFilterModel = new DstChannelFilterModel();
        dstChannelFilterModel.setInstId(routerReq.getInstId());
        dstChannelFilterModel.setTxnNum(routerReq.getTxnNum());
        dstChannelFilterModel.setDstChannelId(completeRouteInfoBean.getDstChannelInfo().getDstChannelId());
        //贷记使用收款方信息
        if (RouteTxnNumEnum.CREDIT.getTxnGroup().equals(routerReq.getTxnGroup())) {
            if (null != routerReq.getPayeeInfo()) {
                if (!StringUtil.isNullorEmpty(routerReq.getPayeeInfo().getAcctType())) {
                    dstChannelFilterModel.setAcctType(routerReq.getPayeeInfo().getAcctType());
                }
                if (!StringUtil.isNullorEmpty(routerReq.getPayeeInfo().getOpenBankNo())) {
                    dstChannelFilterModel.setOpenBankNo(routerReq.getPayeeInfo().getOpenBankNo());
                }
                if (!StringUtil.isNullorEmpty(routerReq.getPayeeInfo().getAcctNo())) {
                    dstChannelFilterModel.setAcctNo(routerReq.getPayeeInfo().getAcctNo());
                }
            }
        }
        //借记及鉴权使用付款方信息
        else {
            if (null != routerReq.getPayerInfo()) {
                if (!StringUtil.isNullorEmpty(routerReq.getPayerInfo().getAcctType())) {
                    dstChannelFilterModel.setAcctType(routerReq.getPayerInfo().getAcctType());
                }
                if (!StringUtil.isNullorEmpty(routerReq.getPayerInfo().getOpenBankNo())) {
                    dstChannelFilterModel.setOpenBankNo(routerReq.getPayerInfo().getOpenBankNo());
                }
                if (!StringUtil.isNullorEmpty(routerReq.getPayerInfo().getAcctNo())) {
                    dstChannelFilterModel.setAcctNo(routerReq.getPayerInfo().getAcctNo());
                }
            }
        }
        dstChannelFilterModel.setTransAmt(routerReq.getTransAmt());
        dstChannelFilterModel.setTxnGroup(routerReq.getTxnGroup());
        return RouterHelper.dstChannelFilter(dstChannelFilterModel, completeRouteInfoBean);
    }
}
